/*
----------------------------------------------------------------------------------------------------
This source file is part of the Gamut Framework
Copyright (c) 2007 by Ryan Holcombe

This program is free software; you can redistribute it and/or modify it under the terms of the GNU
Lesser General Public License as published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
----------------------------------------------------------------------------------------------------
*/


#ifndef _XMLParser_h_
#define _XMLParser_h_

#include <boost/any.hpp>
#include <string>

// tinyxml forward declarations
class TiXmlElement;
class TiXmlDocument;

namespace Gamut
{
	/** 3 floats */
	struct vector3
	{
		vector3() { }
		vector3(float _x, float _y, float _z) :
			x(_x), y(_y), z(_z) { }

		float x, y, z;
	};
	
	/** holds attribute information for a given element */
	struct XMLElement
	{
		TiXmlElement *pos;			//!< tinyxml reference to the current position in the file
		std::string title;			//!< title of the element
		std::string name;			//!< name of the value
		boost::any value;			//!< actual value of the element
		std::string type;			//!< value type (string, int, float, vector3)
	};
	
	/**
 		XMLParser will use tinyxml to load and serialize a xml file
	 	\remarks Simple example on how to read an xml file:\n\n
	 
	 		\<Graphics\>\n
		 		\<Resolution value="640 x 480" type="string" /\>\n
		 		\<FullScreen value="No" type="string" /\>\n
	 		\</Graphics /\>\n
	 		\<Plugins /\>\n
	 			\<Load value="Terrain" type="string" /\>\n\n
	 
	 		1. Load the file, current points at "Graphics"\n
	 		2. Use stepInto(), now points at "Resolution"\n
			3. Use getValueAsString() to get the value\n
			4. Use next(), now points at FullScreen()\n
			5. Use getValueAsString()\n
			5. Use stepBack(), now points at "Graphics"\n
			6. Use findSibling(Plugins), now points at "Plugins"\n
			7. Use stepInto, now points at "Load"\n
			8. Use getValueAsString() to get the value\n
			9. Close the file
	*/

	class XMLParser
	{
	private:
		/**
			\brief sets the values from the tinyxml element
			\param[in] element pointer to XMLElement object to set
			\param[in] tixml reference to the tinyxml position in the file
			\remarks
				If tixml is NULL, then the position reference in XMLElement will be used
		*/
		void _setElementValues(XMLElement *element, TiXmlElement *tixml = NULL);


		/**
			\brief sets the value of the element based on the variable type
			\param[in] element the value of this element will be set
			\param[in] value the value in string format
			\param[in] type value type: string, float, int, or vector3
			\remarks
				This function is called automatically whenever _setElementValues is called
		*/			
		void _setValue(XMLElement *element, const std::string& value, const std::string& type);

	protected:
		XMLElement		mCurrentPos;			//!< current element position in the xml file
		TiXmlDocument	*mDoc;					//!< xml file loaded into tinyxml
		
	public:
		/** Default constructor
		*/
		XMLParser();


		/** Default constructor
			\brief loads an xml file
		*/
		XMLParser(const std::string& file);
			
		
		/** Default deconstructor
			\brief unloads all allocated resources
		*/
		virtual ~XMLParser();
		
		
		/**
			\brief loads a file and makes a list of all existing elements
		*/
		void load(const std::string& file);
			
		
		/**
			\brief returns the current position in the xml file
			\return a pointer to the current position
		*/
		const XMLElement* get() const { return &mCurrentPos; }
		
		/**
			\brief goes down one level to the sub elements that exist within this element
			\return 0 if we are at the bottom
		*/
		bool stepInto();
		
		
		/**
			\brief goes up one level from this sub element
			\return 0 if we are at the top
		*/
		bool stepBack();
		
		
		/**
			\brief goes to the next element in the current level
			\param[in] title if passed, looks for the next sibling with the same title
			\return 0 if we are at the end
		*/
		bool next(const std::string& title = "");
		
		
		/**
			\brief goes to the previous element in the current level
			\return 0 if we are at the beginning
		*/
		bool previous();
		
		
		/**
			\brief searching for the first element with the given title in the current level
			\return 1 if found, 0 otherwise
		*/
		bool findSibling(const std::string& title);
			
					
		/**
			\brief searching for the first child element with the given title
			\return 1 if found, 0 otherwise
		*/
		bool findChild(const std::string& title);
							
					
		/**
			\brief returns to the first element in the file
		*/
		void root();
		
			
		/**
			\brief returns the value in mCurrentPos.title
			\return an empty string if the value doesn't exist
		*/
		const std::string& getTitle();
		
			
		/**
			\brief returns the value in mCurrentPos.name
			\return an empty string if the value doesn't exist
		*/
		const std::string& getName();
		
			
		/**
			\brief returns the value in mCurrentPos.type
			\return an empty string if the value doesn't exist
		*/
		const std::string& getType();

			
		/**
			\brief returns the value in mCurrentPos.value
		 	\return the value as a boost::any type
		*/
		boost::any getValue();

			
		/**
			\brief returns the value in mCurrentPos
			\return value converted to a float
			\remarks
			 	This will throw an exception if mCurrentPos does not have a value associated with it
			 	or if the value is not of this type			 	
		*/
		float getValueAsFloat();
							
					
		/**
			\brief returns the value in mCurrentPos
			\return value converted to an integer
			\remarks
			 	This will throw an exception if mCurrentPos does not have a value associated with it
			 	or if the value is not of this type			 	
		*/
		int getValueAsInt();
							
					
		/**
			\brief returns the value in mCurrentPos
			\return value converted to a string
			\remarks
			 	This will throw an exception if mCurrentPos does not have a value associated with it
			 	or if the value is not of this type			 	
		*/
		const std::string getValueAsString();
							
					
		/**
			\brief returns the value in mCurrentPos
			\return value converted to a vector3
			\remarks
			 	This will throw an exception if mCurrentPos does not have a value associated with it
			 	or if the value is not of this type			 	
		*/
		vector3 getValueAsVector3();


		/**
			\brief outputs the xml file, broken up into its components, to stdout
			\remarks
				file must be loaded before this can be called
		*/
		void _outputToStdout();
	};
}



#endif
